﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FormulParserNamespace
{
    public class PostfixParser : FormulParserInterface
    {
        protected string inputString;
        protected List<Operation> operations;
        Stack<string> postfix;
        List<string> elements;
        List<Variable> variables;

        private List<Operation> setStandartOperations()
        {
            List<Operation> res = new List<Operation>();

            res.Add(new Addition()); 
            res.Add(new Substraction());
            res.Add(new Multiplication());
            res.Add(new Division());
            res.Add(new NaturalLogarithm());
            res.Add(new Exponent());
            res.Add(new Involution());
            res.Add(new Sinus());
            res.Add(new Cosinus());
            res.Add(new LeftBracket());

            return res;
        }

        public PostfixParser(string _formul)
        {
            inputString = string.Copy(_formul);
            operations = setStandartOperations();
            elements = parseToElement();
            variables = SetVariablesSet();
            postfix = parseToPostfix();
        }

        public void setFormul(string _formul)
        {
            inputString = string.Copy(_formul);
        }

        public string getFormul()
        {
            return string.Copy(inputString);
        }

        public List<Variable> getVariables()
        {
            List<Variable> result = new List<Variable>();
            foreach (Variable var in variables)
            {
                Variable tmp = new Variable(var.getName(), var.getCurrentValue());
                result.Add(tmp);
            }
            return result;
        }

        public int getVariablesCount()
        {
            return variables.Count;
        }

        public double getVariableValue(string _variable)
        {
            for (int i = 0; i < getVariablesCount(); i++)
            {
                if (_variable == variables[i].getName())
                    return variables[i].getCurrentValue();
            }
            throw new VariableNotExist(_variable);            
        }

        public double getVariableValue(Variable _variable)
        {
            return getVariableValue(_variable.getName());
        }

        List<string> parseToElement()
        {
            List<string> res = new List<string>();
            int i = 0;
            while (i < inputString.Length)
            {
                string tmp = "";

                switch (inputString[i])
                {
                    case ' ':
                        tmp = " ";
                        break;
                    case '+':
                        tmp = "+";
                        break;
                    case '-':
                        tmp = "-";
                        break;
                    case '*':
                        tmp = "*";
                        break;
                    case '/':
                        tmp = "/";
                        break;
                    case '^':
                        tmp = "^";
                        break;
                    case '(':
                        tmp = "(";
                        break;
                    case ')':
                        tmp = ")";
                        break;
                    default:
                        break;
                }
                bool flag = false;
                while (inputString[i] == '0' || inputString[i] == '1' || inputString[i] == '2' ||
                    inputString[i] == '3' || inputString[i] == '4' || inputString[i] == '5' ||
                    inputString[i] == '6' || inputString[i] == '7' || inputString[i] == '8' ||
                    inputString[i] == '9' || inputString[i] == ',')
                {
                    flag = true;
                    tmp += inputString[i];
                    i++;
                    if (i >= inputString.Length) break;
                }
                if (tmp == "")
                {
                    while (inputString[i] != ' ' && inputString[i] != '+' && inputString[i] != '-' &&
                        inputString[i] != '*' && inputString[i] != '/' && inputString[i] != '^' &&
                        inputString[i] != '(' && inputString[i] != ')')
                    {
                        flag = true;
                        tmp += inputString[i];
                        i++;
                        if (i >= inputString.Length) break;
                    }
                }
                if (flag) i--;
                if (tmp != " ") res.Add(tmp);
                i++;
            }
            return res;
        }

        public void addOperation(Operation _operation)
        {
            if (operations.Contains(_operation)) throw new OperationAlreadyExist(_operation.getSymbol());
            else operations.Add(_operation);
        }

        public void deleteOperation(Operation _operation)
        {
            if (operations.Contains(_operation)) operations.Remove(_operation);
        }

        public void deleteOperation(string _operationSymbol)
        {
            for (int i = 0; i < operations.Count; i++)
            {
                if (operations[i].getSymbol() == _operationSymbol)
                {
                    operations.RemoveAt(i);
                    break;
                }
            }
        }

        List<Variable> SetVariablesSet()
        {
            List<Variable> res = new List<Variable>();

            List<string> operation = new List<string>();
            foreach (Operation op in operations)
            {
                operation.Add(op.getSymbol());
            }

            foreach (string var in elements)
            {
                if (var[0] != '+' && var[0] != '-' && var[0] != '*' && var[0] != '/' &&
                    var[0] != '^' && var[0] != '(' && var[0] != ')' &&
                    var[0] != '0' && var[0] != '1' && var[0] != '2' && var[0] != '3' &&
                    var[0] != '4' && var[0] != '5' && var[0] != '6' && var[0] != '7' &&
                    var[0] != '8' && var[0] != '9' && !(operation.Contains(var))) 
                    res.Add( new Variable(var));
            }

            return res;
        }

        public bool isExist(Variable var)
        {
            return isExist(var.getName());
        }

        public bool isExist(string var)
        {
            for (int i = 0; i < variables.Count; i++)
            {
                if (var == variables[i].getName()) return true;
            }
            return false;
        }

        public bool canEvaluate()
        {
            throw new MethodNotRealise("canEvaluate()");
            //return true;
        }

        public void setVariable(Variable _variable)
        {
            if (!isExist(_variable)) throw new VariableNotExist(_variable.getName());
            for (int i = 0; i < variables.Count; i++)
            {
                if (_variable == variables[i]) 
                    variables[i].setValue(_variable.getCurrentValue());
            }            
        }

        public void setVariable(string _variable)
        {
            throw new MethodNotRealise("setVariable(string _variable)");
            /*string name = "";
            string value = "";
            for(int i = 0; i<_variable.Length; i++)
            {
                if (_variable[i] != ' ' && _variable[i] != '=') name.Insert(name.Length, _variable[i]);
            }*/
        }

        public Variable getVariable(string _variable)
        {
            if (!isExist(_variable)) throw new VariableNotExist(_variable);
            Variable res = new Variable("unknown");
            for (int i = 0; i < getVariablesCount(); i++)
            {
                if (_variable == variables[i].getName())
                { 
                    res = new Variable(variables[i].getName(), variables[i].getCurrentValue());
                }
            }
            return res;
        }

        public double evaluate()
        {
            //evaluate
            double tmp2 = 0;
            Stack<double> tmp = new Stack<double>();
            Stack<string> _postfix = new Stack<string>(postfix.ToArray());
            while (_postfix.Count > 0)
            {
                string currentElement = _postfix.Pop();
                if (isOperationExist(currentElement))//if operation
                {
                    for (int i = 0; i < operations.Count; i++)
                    {
                        if (currentElement == operations[i].getSymbol())
                        {
                            if (operations[i].getOperandsCount() == 2)
                            {
                                double rightOperand = tmp.Pop();
                                double leftOperand = tmp.Pop();
                                tmp.Push(operations[i].evaluate(leftOperand, rightOperand));
                            }
                            else if (operations[i].getOperandsCount() == 1)
                            {
                                double operand = tmp.Pop();
                                tmp.Push(operations[i].evaluate(operand));
                            }
                            break;
                        }
                    }
                }
                else if (isExist(currentElement))//if variable push value in tmp
                {
                    tmp.Push(getVariableValue(currentElement));
                }
                else if ( double.TryParse(currentElement,
                    System.Globalization.NumberStyles.Number,
                    System.Globalization.NumberFormatInfo.CurrentInfo,
                    out tmp2))
                {
                    tmp.Push(tmp2);
                }
                else
                {
                    throw new ParsingError(inputString);
                }
            }
            
            return tmp.Pop();
        }

        public double evaluate(List<Variable> _variables)
        {
            //set variables value from list
            variables.Clear();
            foreach (Variable var in _variables)
            {
                string _name = string.Copy(var.getName());
                double _value = var.getCurrentValue();
                Variable tmp = new Variable(_name, _value);
                variables.Add(tmp);
            }
            //evaluate
            return evaluate();
        }

        public double evaluate(string _variablesString)
        {
            throw new MethodNotRealise("evaluate(string _variablesString)");
            
            /*//parse to list            
            List<Variable> _variablesList = new List<Variable>();
            //evaluate
            return evaluate(_variablesList);*/
        }

        public bool isOperationExist(string _op)
        {
            for (int i = 0; i < operations.Count; i++)
            {
                if (_op == operations[i].getSymbol()) return true;
            }
            return false;
        }

        Stack<string> parseToPostfix()
        {
            Stack<string> res = new Stack<string>();
            Stack<string> tmp = new Stack<string>();

            int i = 0;
            while (i < elements.Count)
            {
                if (!(isOperationExist(elements[i])) && elements[i] != ")") res.Push(elements[i]);
                else if (isOperationExist(elements[i]) && elements[i] != "(")
                {
                    if (tmp.Count == 0) tmp.Push(elements[i]);
                    else if (getPriority(tmp.Peek()) < getPriority(elements[i])) tmp.Push(elements[i]);
                    else
                    {
                        while (tmp.Count != 0)
                        {
                            res.Push(tmp.Peek());
                            tmp.Pop();
                            if (tmp.Count != 0) if (getPriority(tmp.Peek()) < getPriority(elements[i])) break;
                        }
                        tmp.Push(elements[i]);
                    }
                }
                else if (elements[i] == "(") tmp.Push(elements[i]);
                else if (elements[i] == ")")
                {
                    while (tmp.Peek() != "(")
                    {
                        res.Push(tmp.Peek());
                        tmp.Pop();
                    }
                    tmp.Pop();
                }
                i++;
            }

            while (tmp.Count != 0)
            {
                res.Push(tmp.Peek());
                tmp.Pop();
            }

            return res;
        }

        int getPriority(string c)
        {
            int res = -1;
            switch (c)
            {
                case "(":
                    res = 1;
                    break;
                case "+":
                    res = 2;
                    break;
                case "-":
                    res = 2;
                    break;
                case "*":
                    res = 3;
                    break;
                case "/":
                    res = 3;
                    break;
                case "^":
                    res = 4;
                    break;
                case "sin":
                    res = 5;
                    break;
                case "cos":
                    res = 5;
                    break;
                case "ln":
                    res = 5;
                    break;
                case "exp":
                    res = 5;
                    break;
            }
            return res;
        }
    }
}
